﻿#region License
/* ---------------------------------------------------------------- *
 *
 * This file is part of the Xcoordination Application Space
 * ("XcoAppSpace") http://www.xcoordination.com
 *
 * Copyright (C) 2009 Xcoordination http://www.xcoordination.com
 *
 * XcoAppSpace is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General
 * Public License as published by the Free Software Foundation.
 *
 * XcoAppSpace is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/
 * or write to the Free Software Foundation, Inc., 51 Franklin
 * Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * ---------------------------------------------------------------- */
#endregion

using System;
using Windows.Networking.Sockets;
using System.Text;
using System.IO;
using System.Linq;
using Windows.Storage.Streams;
using XcoAppSpaces.Contracts.Logging;
using XcoAppSpaces.Contracts.Transport;
using System.Collections.Generic;
using System.Diagnostics;

namespace XcoAppSpaces.Transport.Sockets
{
    ///<summary>
    /// Helperclass for XcoMessage convert for transfer
    ///</summary>
    internal class TransferHelper
    {
        private static readonly int IntByteLength = BitConverter.GetBytes(1).Length;

        private readonly IXcoLogger log;
        
        ///<summary>
        /// Helper class for XcoMessages converts
        ///</summary>
        public TransferHelper()
        {
            log = XcoLoggerFactory.CreateInstance(GetType());
        }


        /// <summary>
        /// Converts the XcoMessage to a byte[]
        /// </summary>
        /// <param name="msg">XcoMessage to convert</param>
        /// <param name="localAddress">The local address of the transport service.</param>
        /// <returns>byte[] instance of the XcoMessage</returns>
        public byte[] Convert(XcoMessage msg, string localAddress)
        {
			try
			{
				if (string.IsNullOrEmpty(localAddress))
				{
					log.Error("Error converting XcoMessage to byte[]: Address is empty!");
					return null;
				}
				byte[] addr = Encoding.UTF8.GetBytes(localAddress);

				if (msg.Content == null || msg.Content.Length == 0)
				{
					log.Error("Error converting XcoMessage to byte[]: Content is null!");
					return null;
				}
                int contentLen = msg.Content.Length;
				int contextLen = msg.CausalityContext != null ? msg.CausalityContext.Length : 0;
				
				byte[] contentInfo = null;
				int contentInfoLen = 0;
				if (!string.IsNullOrEmpty(msg.ContentInfo))
				{
					contentInfo = Encoding.UTF8.GetBytes(msg.ContentInfo);
					contentInfoLen = contentInfo.Length;
				}

				var memStream = new MemoryStream();
				byte[] len = BitConverter.GetBytes(addr.Length + contextLen + contentLen + contentInfoLen + 4*IntByteLength);
				memStream.Write(len, 0, len.Length);
				len = BitConverter.GetBytes(addr.Length);
				memStream.Write(len, 0, len.Length);
				len = BitConverter.GetBytes(contentInfoLen);
				memStream.Write(len, 0, len.Length);
				len = BitConverter.GetBytes(contextLen);
				memStream.Write(len, 0, len.Length);
				len = BitConverter.GetBytes(contentLen);
				memStream.Write(len, 0, len.Length);
				memStream.Write(addr, 0, addr.Length);

				if (contentInfo != null && contentInfo.Length > 0)
				{
					memStream.Write(contentInfo, 0, contentInfo.Length);
				}
				if (msg.CausalityContext != null && msg.CausalityContext.Length > 0)
				{
					memStream.Write(msg.CausalityContext, 0, msg.CausalityContext.Length);
				}
				
				memStream.Write(msg.Content, 0, msg.Content.Length);
				
				return memStream.ToArray();
			}
			catch (Exception err)
			{
				log.Error("Error converting XcoMessage to byte[]", err);
				return null;
			}
        }

        /// <summary>
        /// Convert byte[] to XcoMessage
        /// </summary>
        /// <param name="reader">read XcoMessage from stream</param>
        /// <param name="remoteAddress">Address of the space where the message came from.</param>
		/// <param name="handleEmptyData">Action that should be called when data is empty</param>
		/// <param name="handleError">Action that should be called when an error is encountered</param>
		/// <returns>new XcoMessage instance</returns>
		public XcoMessage Convert(DataReader reader, out string remoteAddress,
			Action handleEmptyData, Action<Exception> handleError)
        {
        	remoteAddress = null;
            try
            {
                var data = new byte[IntByteLength];
                var dataLength = reader.Read(data);
				if (dataLength == 0)
				{
					handleEmptyData(); //stream was closed -> close tcpclient
					return null;
				}
                int gesamtLen = BitConverter.ToInt32(data, 0);
                
                data = new byte[IntByteLength];
                reader.Read(data);
                int addrLen = BitConverter.ToInt32(data, 0);

                data = new byte[IntByteLength];
                reader.Read(data);
                int contenInfoLen = BitConverter.ToInt32(data, 0);

                data = new byte[IntByteLength];
                reader.Read(data);
                int contextLen = BitConverter.ToInt32(data, 0);

                data = new byte[IntByteLength];
                reader.Read(data);
                int contentLen = BitConverter.ToInt32(data, 0);

                //Debug.Assert(gesamtLen == addrLen + contextLen + contentLen +contenInfoLen+ 4 * IntByteLength, "Length error");

                string addrStr = null;
                string contentInfoStr = null;
                byte[] addr;
                int readLen = ReadData(reader, addrLen, out addr);
                if (readLen != addrLen)
                {
                    return null;
                }
                if (readLen > 0)
                {
                    addrStr = Encoding.UTF8.GetString(addr, 0, addr.Length);
                }
                byte[] contenInfo;
                readLen = ReadData(reader, contenInfoLen, out contenInfo);
                if (readLen != contenInfoLen)
                {
                    return null;
                }
                if (readLen > 0)
                {
                    contentInfoStr = Encoding.UTF8.GetString(contenInfo, 0, contenInfo.Length);
                }
                byte[] context;
                readLen = ReadData(reader, contextLen, out context);
                if (readLen != contextLen)
                {
                    return null;
                }
                byte[] content;
                readLen = ReadData(reader, contentLen, out content);
                if (readLen != contentLen)
                {
                    return null;
                }

            	remoteAddress = addrStr;
                return new XcoMessage
                           {
                               //RemoteAddress = addrStr,
                               ContentInfo = contentInfoStr,
                               CausalityContext = context,
                               Content = content
                           };

            }
            catch (Exception err)
            {
				handleError(err);
            }
            return null;
        }

        /// <summary>
        /// Read data from stream
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="gesamtLen"></param>
        /// <param name="content"></param>
        /// <returns></returns>
        private static int ReadData(DataReader reader, int gesamtLen, out  byte[] content)
        {
            if (gesamtLen <= 0)
            {
                content = null;
                return 0;
            }
            content = new byte[gesamtLen];
            int lenToRead = gesamtLen;

			reader.Read(content);
			
			//int readCount = 0;
			//while (true)
			//{
			//	int readLen = reader.Read(content, readCount, lenToRead);
			//	readCount += readLen;
			//	if (readCount >= gesamtLen)
			//	{
			//		break;
			//	}
			//	lenToRead -= readLen;
			//}
	        return gesamtLen;  //readCount;
        }

#if SILVERLIGHT
		//with silverlight conversion data is not read continuosly from a stream, but received as arrays
		//therefore operate directly on the arrays and not on streams
		//these methods may also be used by the normal appspace once the tcp transport service is changed to async

		private byte[] storedData;
		public IEnumerable<XcoMessage> Convert(byte[] msg, out string remoteAddress, Action<Exception> handleError)
		{
			if (storedData != null)
				msg =  storedData.Concat(msg);
			remoteAddress = null;
			var messages = new List<XcoMessage>();
			try
			{
				int offset = 0;
				while (true) //the byte array could contain multiple messages in a row
				{
					if (offset >= msg.Length)
					{
						storedData = null;
						break; //no more messages
					}

					int gesamtLen = ReadInt(msg, ref offset);
					if ((msg.Length - offset) < gesamtLen)
					{
						//data too short - wait for rest
						if (messages.Count > 0)
							storedData = msg.SubArray(offset - 4); //msg.Skip(offset - 4).ToArray(); 
						else
							storedData = msg;
						break;
					}

					int addrLen = ReadInt(msg, ref offset);
					int contenInfoLen = ReadInt(msg, ref offset);
					int contextLen = ReadInt(msg, ref offset);
					int contentLen = ReadInt(msg, ref offset);

					Debug.Assert(gesamtLen == addrLen + contextLen + contentLen +contenInfoLen+ 4 * IntByteLength, "Length error");

					string addrStr = null;
					byte[] addr = ReadData(msg, addrLen, ref offset);
					addrStr = Encoding.UTF8.GetString(addr, 0, addr.Length);

					string contentInfoStr = null;
					byte[] contenInfo = ReadData(msg, contenInfoLen, ref offset);
					contentInfoStr = Encoding.UTF8.GetString(contenInfo, 0, contenInfo.Length);

					byte[] context = ReadData(msg, contextLen, ref offset);
					byte[] content = ReadData(msg, contentLen, ref offset);

					remoteAddress = addrStr;
					messages.Add(new XcoMessage
					{
						ContentInfo = contentInfoStr,
						CausalityContext = context,
						Content = content
					});
				}
			}
			catch (Exception err)
			{
				handleError(err);
			}
			return messages;
		}
		
		private int ReadInt(byte[] data, ref int offset)
		{
			var bytes = ReadData(data, IntByteLength, ref offset);
			return BitConverter.ToInt32(bytes, 0);
		}

		private byte[] ReadData(byte[] data, int length, ref int offset)
		{
			if (length <= 0)
			{
				return null;
			}
			if (length + offset > data.Length)
			{
				//not enough data...
				log.Error("XcoTCPTransportService TransferHelper: Error reading data - too short!!");
				return null;
			}

			var bytes = data.SubArray(offset, length); //data.Skip(offset).Take(length).ToArray();
			offset += length;
			return bytes;
		}
#endif
	}

	
}
